#include "SendStrategy.h"

#include "LogInterface.h"
#include "MyAssert.h"
#include "SdpParser.h"
#include "MyIO.h"
#include "OS.h"

using namespace std;

CSendStrategy::CSendStrategy()
{
	m_Status = SS_ALL;
	m_StatusChanged = FALSE;
	m_Mode = SM_ALL;
	m_CurSentRate = 1000;

	m_WindowSize = 100;
	m_CurCount = 0;
	m_CurSucc = 0;
	m_LastTime = 0;
	m_CurTotalLen = 0;
	m_BetterByteRate = 0;
	m_CurSuccLen = 0;
	m_CurByteRate = 1000;
}

CSendStrategy::~CSendStrategy()
{
}

void CSendStrategy::Initialize(const std::string &strSDP, STRATEGY_MODE vMode)
{
	m_Mode = vMode;

	// parser SDP and setup StreamInfos
	CSdpParser SdpParser;
	SdpParser.SetAndParser(strSDP);
	CSdpMediaAnnounceItem* pItem = NULL;
	
	for(Int32 i=0; i<SdpParser.GetTrackNum(); i++)
	{
		pItem = SdpParser.GetMediaAnnounceItem(i);
		if(pItem != NULL)
		{
			SStreamInfo info;
			info.TrackID = pItem->GetTrackID();

			//Stream type
			if(pItem->GetMediaType() == "video")
				info.StreamType = ST_VIDEO;
			else if(pItem->GetMediaType() == "audio")
				info.StreamType = ST_AUDIO;
			else
				info.StreamType = ST_UNKOWN;

			//Codec type
			if(info.StreamType == ST_VIDEO)
			{
				string strCodec = pItem->GetCodec();
				if(strCodec.find("mp4") != string::npos || strCodec.find("mpeg4") != string::npos)
				{
					info.CodecType = CT_MPEG4;
				}
				else if(strCodec.find("h264") != string::npos)
				{
					info.CodecType = CT_H264;
				}
				else
					info.CodecType = CT_UNKNOWN;
			}
			else
				info.CodecType = CT_AUDIO;

			//others
			info.LastMarkBit = TRUE;
			info.LastSeqNo = 0;
			info.LastTimeStamp = 0;
			info.FrameType= FT_KEYFRAME;

			m_StreamInfos[info.TrackID] = info;
		}
	}
}

Bool CSendStrategy::IsNeedSend(UInt32 vTrackID, const char *pData, UInt32 vLen, FRAME_TYPE vType)
{
	if(m_Mode == SM_ALL)
		return TRUE;
	
	//check current strategy status
	{// SS_KEYFRAME or SS_AUDIO
		StreamInfoIterator it = m_StreamInfos.find(vTrackID);
		if( it != m_StreamInfos.end())
		{
			if(it->second.StreamType == ST_AUDIO
				|| it->second.StreamType == ST_UNKOWN)
			{//AUDIO
				return TRUE;
			}
			else
			{//VIDEO
				///TOdO some problem(low efficient) with H264 keyframe
				if(it->second.CodecType == CT_H264)
					return TRUE;

				//check payload type
				if(vType == FT_UNKOWN)
				{
					DetectFrameType(pData,vLen, &it->second, vType);
				}
				

/*
				//prevent screen garbag when change status, wait for keyframe
				if(m_StatusChanged)
				{
					if(vType == FT_KEYFRAME)
					{
						m_StatusChanged = FALSE;
					}
					else
						return TRUE;
				}*/

				

				if(m_Status == SS_ALL)
					return TRUE;
				else if(m_Status == SS_KEYFRAME)
				{
					switch(vType)
					{
					case FT_KEYFRAME:
					case FT_OTHERS:
						return TRUE;
						
					case FT_VIDEO_P:
					case FT_VIDEO_B:
					case FT_VIDEO_OTHER:
					case FT_UNKOWN:
						return FALSE;
						
					default:
						Assert(FALSE);
					}
				}
				else if(m_Status == SS_AUDIO)
				{
					return FALSE;
				}
				else
				{
					Assert(FALSE);
				}
				
			}
		}
	}

	return FALSE;
}

void CSendStrategy::SetSentRate(UInt32 vRate)
{
	m_CurSentRate = vRate;
	Schedule();
}

Bool CSendStrategy::Schedule()
{
	STRATEGY_STATUS tmp = m_Status;

	if(m_CurSentRate >= 950)
	{
		m_Status--;
	}
	else if(m_CurSentRate < 600)
	{
		m_Status ++;
	}

	if(m_Status < SS_ALL)
		m_Status = SS_ALL;
	else if(m_Status > SS_AUDIO)
		m_Status = SS_AUDIO;

	m_StatusChanged = (m_Status != tmp);

	return TRUE;
}

/*
 *	H264 keyframe id
 *	nal in payload: 00 00 00 01 xx
 *					[04] & 0x1f = 0x5
 *	So need to seek all payload. it is low effeceint!
 *
 *	MPEG4 frame id
 *	payload: 
 *		[04] & 0xc0 = 0x00  I frame
 *		[04] & 0xc0 = 0x40  P frame
 *		[04] & 0xc0 = 0x80  B frame
 *		[04] & 0xc0 = 0xc0  S frame(????)

 * OUTPUT
 *	outType:
 *		PT_OTHERS: unknown the payload type(codec).
 *		PT_VIDEO_OTHER: unknown frame type. means not keyframe!
*/
static char H264_KEYFRAME_ID[]= {0,0,0,1};

void CSendStrategy::DetectFrameType(const char *pData, UInt32 vLen, SStreamInfo *pInfo, FRAME_TYPE &outType)
{
	Assert(pInfo && pInfo->StreamType == ST_VIDEO);
	if(pInfo && pInfo->StreamType == ST_VIDEO)
	{
		UInt16 iSeqNo = ntohs(*(UInt16 *)(pData +2));
		UInt32 iTimeStamp = ntohl(*(UInt32 *)(pData +4));

		#define RTP_HEADER_LEN 12
		//if(pInfo->LastMarkBit)
		if(pInfo->LastTimeStamp != iTimeStamp)
		{//current is new frame. so detect the payload type
			if(pInfo->CodecType == CT_MPEG4)
			{
				switch(pData[RTP_HEADER_LEN + 04] & 0xc0)
				{
				case 0x00: pInfo->FrameType = FT_KEYFRAME;
						printf("-- DEBUG INFO -- MPEG4 codec keyframe found\n");
						break;
				case 0x40: pInfo->FrameType = FT_VIDEO_P;
						break;
				case 0x80: pInfo->FrameType = FT_VIDEO_B;
						break;
				case 0xc0:
				default://S frame
					pInfo->FrameType = FT_VIDEO_OTHER;
				}
			}
			else if(pInfo->CodecType == CT_H264)
			{
			}
			else
			{
				outType = FT_OTHERS;
				return ;
			}

		}

		//////////////////////////////////////////////////////////////////////////
		///TODO since there are too many nal in codec frame, so need to look up all nals to find the flag.
		// it is too slow to find the H264 keyframe.
		if(pInfo->CodecType == CT_H264)
		{
			///TODO it is too slow to find the H264 keyframe
			char * pActData = (char*)pData + RTP_HEADER_LEN;
			Bool bFound = FALSE;
			while(pActData < pData + vLen)
			{
				if( ((pActData[04] & 0x1f) == 0x05) &&
					memcmp(pActData,H264_KEYFRAME_ID,4) == 0)
				{
					Output<<"-- DEBUG INFO -- found keyframe, offset of rtp packet is "<<(UInt32)(pActData - pData)<<Endl;
					bFound = TRUE;
					break;
				}
				pActData ++;
			}
			
			if(bFound)
				pInfo->FrameType = FT_KEYFRAME;
			else
				pInfo->FrameType = FT_VIDEO_OTHER;
		}
		//////////////////////////////////////////////////////////////////////////
		

		//RTP packet format see the bottom of documentation
		pInfo->LastSeqNo = iSeqNo;
		pInfo->LastTimeStamp = iTimeStamp;
		pInfo->LastMarkBit = pData[1] & 0x80 ? TRUE : FALSE;
		outType = pInfo->FrameType;
	}
	else
		outType = FT_UNKOWN;
}

void CSendStrategy::ReStartStrategy(UInt32 vWindowSize)
{
	StreamInfoIterator it = m_StreamInfos.begin();
	for(; it != m_StreamInfos.end(); ++it)
	{
		it->second.LastMarkBit = TRUE;
		it->second.LastSeqNo = 0;
		it->second.LastTimeStamp = 0;
		it->second.FrameType = FT_KEYFRAME;
	}

	m_WindowSize = vWindowSize;
	m_CurCount = 0;
	m_CurSucc = 0;
	m_LastTime = OS::Milliseconds() + 10000;
	m_CurTotalLen = 0;
	m_CurSuccLen = 0;
	m_CurByteRate = 1000;
	m_StatusChanged = FALSE;
	m_Status = SS_ALL;
}

void CSendStrategy::StatisticSendResult(Bool vSucc, UInt32 vLen)
{
	m_CurCount++;
	m_CurTotalLen += vLen;
	if(vSucc)
	{
		m_CurSucc++;
		m_CurSuccLen += vLen;
	}

	if(m_CurCount > m_WindowSize)
	{
		Int64 iNow = OS::Milliseconds();
		UInt32 iKeepTime = 0;
		switch(m_Status)
		{
		case SS_ALL: iKeepTime = KT_ALL;
			break;
		case SS_KEYFRAME: iKeepTime = KT_KEYFRAME;
			break;
		case SS_AUDIO: iKeepTime = KT_AUDIO;
			break;
		default:
			Assert(FALSE);
			iKeepTime = KT_ALL;
		}

		if(iNow - m_LastTime > iKeepTime)
		{
			m_CurSentRate = m_CurSucc * 1000 / m_CurCount;

			//calc byterate
			m_CurByteRate = 1000;
			if(m_CurTotalLen > 0)
				 m_CurByteRate = m_CurSuccLen * 1000 /(iNow - m_LastTime);

			Schedule();

			m_CurSucc = 0;
			m_CurCount = 0;
			m_CurSuccLen = 0;
			m_CurTotalLen = 0;
			
			m_LastTime = iNow;
		}

	}
}

void CSendStrategy::GetFrameType(UInt32 vTrackID, const char *pData, UInt32 vLen, FRAME_TYPE &vType)
{
	StreamInfoIterator it = m_StreamInfos.find(vTrackID);
	if( it != m_StreamInfos.end())
	{
		if(it->second.StreamType == ST_AUDIO
			|| it->second.StreamType == ST_UNKOWN)
		{//AUDIO
			vType = FT_KEYFRAME;
			return ;
		}
		else
		{
			if( it->second.CodecType == CT_UNKNOWN )
				vType = FT_KEYFRAME;
			else
				DetectFrameType(pData, vLen, &it->second, vType);
		}
	}
}

void CSendStrategy::StartFrameTypeDetect()
{
	for(StreamInfoIterator it = m_StreamInfos.begin(); it != m_StreamInfos.end(); ++it)
	{
		it->second.LastMarkBit = TRUE;
	}
}

/*
The RTP header has the following format:

0                   1                   2                   3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|V=2|P|X|  CC   |M|     PT      |       sequence number         |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                           timestamp                           |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|           synchronization source (SSRC) identifier            |
+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
|            contributing source (CSRC) identifiers             |
|                             ....                              |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
*/
